package lv.solcraft.replication.client.http;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import lv.solcraft.replication.common.OperDTO;
import lv.solcraft.replication.common.OperStateDTO;
import lv.solcraft.replication.common.ReplicationConst;
import lv.solcraft.replication.common.OperDTO.OperState;
import net.sf.json.JSONArray;
import net.sf.json.JSONNull;
import net.sf.json.JSONObject;
import net.sf.json.util.JSONUtils;

import org.springframework.util.Assert;

import com.noelios.restlet.util.Base64;

public class OperConverter {

    private static Map<String, String> clientToServerOperTypes = new HashMap<String, String>();
    private static Map<String, String> serverToClientOperTypes = new HashMap<String, String>();
    private static Map<String, String> clientToServerOperStates = new HashMap<String, String>();

    static {
        clientToServerOperTypes.put(ReplicationConst.TYPE_INSERT, "INSERT");
        clientToServerOperTypes.put(ReplicationConst.TYPE_UPDATE, "UPDATE");
        clientToServerOperTypes.put(ReplicationConst.TYPE_DELETE, "DELETE");
        clientToServerOperTypes = Collections.unmodifiableMap(clientToServerOperTypes);
        for (String client : clientToServerOperTypes.keySet()) {
            serverToClientOperTypes.put(clientToServerOperTypes.get(client), client);
        }
        serverToClientOperTypes = Collections.unmodifiableMap(serverToClientOperTypes);
        //
        clientToServerOperStates.put(ReplicationConst.STATE_COMMITED, "COMMITED");
        clientToServerOperStates.put(ReplicationConst.STATE_ERROR, "ERROR");
        clientToServerOperStates.put(ReplicationConst.STATE_IGNORED, "IGNORED");
        clientToServerOperStates.put(ReplicationConst.STATE_NEW, "NEW");
        clientToServerOperStates = Collections.unmodifiableMap(clientToServerOperStates);
    }

    public static JSONObject convert(OperDTO oper) {
        JSONObject json = new JSONObject();
        JSONObject id = new JSONObject();
        id.put("originSiteId", oper.getOperOwnerSiteId());
        id.put("originOperId", oper.getOperId());
        json.put("id", id);
        JSONObject record = new JSONObject();
        record.put("table", oper.getTable());
        record.put("recordSiteId", oper.getRecordSiteId());
        record.put("recordId", oper.getRecordId());
        json.put("record", record);
        json.put("type", convertTypeFromClientToServer(oper.getType()));
        JSONArray fields = new JSONArray();
        for (OperDTO.Field field : oper.getFields()) {
            JSONObject fieldJson = new JSONObject();
            fieldJson.put("name", field.getName());
            fieldJson.put("value", field.getValue());
            fields.add(fieldJson);
        }
        json.put("fields", fields);
        JSONArray infoFields = new JSONArray();
        for (OperDTO.Field field : oper.getDescrFields()) {
            JSONObject fieldJson = new JSONObject();
            fieldJson.put("name", field.getName());
            fieldJson.put("value", field.getValue());
            infoFields.add(fieldJson);
        }
        json.put("infoFields", infoFields);
        JSONArray blobFields = new JSONArray();
        for (Entry<String, byte[]> entry : oper.getBlobFields().entrySet()) {
            JSONObject fieldJson = new JSONObject();
            fieldJson.put("name", entry.getKey());
            fieldJson.put("value", entry.getValue() == null ? JSONNull.getInstance() : Base64
                    .encode(entry.getValue(), true));
            blobFields.add(fieldJson);
        }
        json.put("blobFields", blobFields);

        JSONArray states = new JSONArray();
        for (OperState state : oper.getOperStates()) {
            JSONObject stateJson = new JSONObject();
            stateJson.put("siteId", state.getSiteId());
            String convertedState = convertStateFromClientToServer(state.getState());
            stateJson.put("state", convertedState);
            stateJson.put("message", state.getMessage() == null ? "" : state.getMessage());
            states.add(stateJson);
        }
        json.put("states", states);
        return json;
    }

    private static String convertTypeFromClientToServer(String type) {
        String serverType = clientToServerOperTypes.get(type);
        Assert.notNull(serverType, "Oper type from client not supported: " + type);
        return serverType;
    }

    private static String convertStateFromClientToServer(String state) {
        String serverState = clientToServerOperStates.get(state);
        Assert.notNull(serverState, "Oper state from client not supported: " + state);
        return serverState;
    }

    private static String convertTypeFromServerToClient(String type) {
        String clientType = serverToClientOperTypes.get(type);
        Assert.notNull(clientType, "Oper type from server not supported: " + type);
        return clientType;
    }

    public static OperDTO convert(JSONObject json) {
        OperDTO oper = new OperDTO();
        Assert.isTrue(json.has("id"), "Oper json doesn't have id element");
        JSONObject id = json.getJSONObject("id");
        oper.setOperId(id.getInt("originOperId"));
        oper.setOperOwnerSiteId(id.getInt("originSiteId"));
        Assert.isTrue(json.has("record"), "Oper json doesn't have record element");
        JSONObject record = json.getJSONObject("record");
        oper.setRecordId(record.getInt("recordId"));
        oper.setRecordSiteId(record.getInt("recordSiteId"));
        oper.setTable(record.getString("table"));
        oper.setType(convertTypeFromServerToClient(json.getString("type")));
        Assert.isTrue(json.has("fields"), "Oper json doesn't have fields element");
        JSONArray fields = json.getJSONArray("fields");
        for (int i = 0; i < fields.size(); i++) {
            JSONObject field = fields.getJSONObject(i);
            String value = null;
            if (!JSONUtils.isNull(field.get("value")))
                value = field.getString("value");
            oper.addField(field.getString("name"), value);
        }
        Assert.isTrue(json.has("infoFields"), "Oper json doesn't have infoFields element");
        JSONArray infoFields = json.getJSONArray("infoFields");
        for (int i = 0; i < infoFields.size(); i++) {
            JSONObject field = infoFields.getJSONObject(i);
            String value = null;
            if (!JSONUtils.isNull(field.get("value")))
                value = field.getString("value");
            oper.addDescrField(field.getString("name"), value);
        }
        JSONArray blobFields =
                json.containsKey("blobFields") ? json.getJSONArray("blobFields") : new JSONArray();
        for (int i = 0; i < blobFields.size(); i++) {
            JSONObject field = blobFields.getJSONObject(i);
            byte[] value = null;
            if (!JSONUtils.isNull(field.get("value")))
                value = Base64.decode(field.getString("value"));
            oper.getBlobFields().put(field.getString("name"), value);
        }
        return oper;
    }

    public static JSONObject convert(OperStateDTO state) {
        JSONObject json = new JSONObject();
        JSONObject id = new JSONObject();
        id.put("originSiteId", state.getOper().getOperOwnerSiteId());
        id.put("originOperId", state.getOper().getOperId());
        json.put("id", id);
        json.put("state", convertStateFromClientToServer(state.getState()));
        json.put("message", state.getMessage());
        return json;
    }
}
